package org.ws.httphelper.support.pipeline.handler.request;

import com.google.common.collect.Sets;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ws.httphelper.common.Constant;
import org.ws.httphelper.core.pipeline.HandlerOrder;
import org.ws.httphelper.core.pipeline.RequestHandler;
import org.ws.httphelper.exception.RequestException;
import org.ws.httphelper.model.RequestContext;
import org.ws.httphelper.model.config.RequestConfig;
import org.ws.httphelper.model.field.ParamFieldType;
import org.ws.httphelper.model.field.ParamField;
import org.ws.httphelper.model.http.HttpMethod;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 生成实际URL
 */
@HandlerOrder(value = 2,level = HandlerOrder.OrderLevel.SYSTEM)
public class BuildUrlHandler implements RequestHandler {

    private static Logger log = LoggerFactory.getLogger(BuildUrlHandler.class.getName());

    private final Pattern paramPattern = Pattern.compile("(\\{([^&/\\}]+)\\})");

    /**
     * 处理URL.
     * 若temple中url为root url，传入url为path，将两者拼接在一起.
     * 若temple中url存在{key}，替换成具体值
     *  GET，HEAD请求根据param重新生成URL
     * @param requestContext 请求context
     * @throws RequestException
     */
    @Override
    public boolean handler(final RequestContext requestContext) throws RequestException {
        String url = requestContext.getUrl();
        final RequestConfig requestConfig = requestContext.getRequestConfig();
        final String rootUrl = requestConfig.getRootUrl();
        if(StringUtils.isBlank(url)){
            if(StringUtils.isBlank(rootUrl)) {
                throw new RequestException("url is blank.");
            }
            else {
                url = rootUrl;
            }
        }
        StringBuilder urlBuilder = new StringBuilder();
        // 入参为url全路径：优先使用
        if(Pattern.matches(Constant.URL_PATTERN,url)){
            urlBuilder.append(url);
        }
        // 若存在rootUrl,并且入参为path
        else if(StringUtils.isNotBlank(rootUrl) && url.startsWith("/")){
            urlBuilder.append(rootUrl).append(url);
        }
        // 其他不处理
        else {
            log.warn("requestContext.url[{}] is not match URL and rootUrl[{}] is blank " +
                    "and requestContext.url is not start with '/'.",url,rootUrl);
            return true;
        }

        Map<String, Object> params = requestContext.getParams();
        String newUrl = buildPathUrl(urlBuilder.toString(),params);
        newUrl = buildParamUrl(newUrl,params,requestConfig);
        requestContext.getRequestData().setUrl(newUrl);
        if(log.isDebugEnabled()) {
            log.debug("build url:{} -> {}", url, newUrl);
        }
        return true;
    }

    /**
     * 对URL中存在{key}的字段进行替换
     * @param url
     * @param params
     * @return
     */
    private String buildPathUrl(final String url,final Map<String,Object> params) {
        if(MapUtils.isEmpty(params)){
            return url;
        }
        StringBuilder urlBuilder = new StringBuilder(url);
        Matcher matcher = paramPattern.matcher(urlBuilder.toString());
        while (matcher.find()) {
            String value = String.valueOf(params.get(matcher.group(2)));
            final int start = matcher.start(1);
            final int end = matcher.end(1);
            urlBuilder.replace(start,end,value);
            matcher = paramPattern.matcher(urlBuilder.toString());
        }
        if(log.isDebugEnabled()) {
            log.debug("build url by path:{}", urlBuilder.toString());
        }
        return urlBuilder.toString();
    }

    /**
     * GET等请求生成key=value连接
     * @param url
     * @param params
     * @param requestConfig
     * @return
     */
    private String buildParamUrl(final String url, final Map<String, Object> params, final RequestConfig requestConfig) {
        if(MapUtils.isEmpty(params)){
            return url;
        }
        final HttpMethod method = requestConfig.getMethod();
        if(method != HttpMethod.GET && method != HttpMethod.HEAD){
            return url;
        }
        StringBuilder urlBuilder = new StringBuilder(url);
        Set<String> existsKey = parser(urlBuilder.toString());
        boolean hasParam = urlBuilder.indexOf("?") != -1;
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            ParamField paramField = requestConfig.getParamFields().get(key);
            if(!existsKey.contains(key)) {
                if(hasParam){
                    urlBuilder.append("&");
                }
                else {
                    urlBuilder.append("?");
                    hasParam = true;
                }
                urlBuilder.append(key)
                        .append("=")
                        .append(value);
            }
            if(paramField != null && paramField.getFieldType() == ParamFieldType.LIST){
                if(!paramField.isRequired() && value == null){
                    continue;
                }
                if(hasParam){
                    urlBuilder.append("&");
                }
                else {
                    urlBuilder.append("?");
                    hasParam = true;
                }
                if(value instanceof Collection){
                    Collection collection = (Collection)value;
                    collection.forEach(s ->
                        urlBuilder.append("&").append(key)
                                .append("=")
                                .append(s)
                    );
                }
                else {
                    if(hasParam){
                        urlBuilder.append("&");
                    }
                    else {
                        urlBuilder.append("?");
                        hasParam = true;
                    }
                    urlBuilder.append(key)
                            .append("=")
                            .append(value);
                }

            }
        }
        if(log.isDebugEnabled()) {
            log.debug("build url by params:{}", urlBuilder.toString());
        }
        return urlBuilder.toString();
    }

    private Set<String> parser(String url){
        Set<String> set = Sets.newHashSet();
        String queryString = url.substring(url.indexOf('?')+1);
        String[] kvs = StringUtils.split(queryString,"&");
        if(kvs != null){
            for (String kv : kvs) {
                String[] arr = StringUtils.split(kv, "=");
                set.add(arr[0]);
            }
        }
        return set;
    }

}
